/*
Copyright (c) 2011 Sanjay Dasgupta (sanjay.dasgupta@gmail.com)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
 */
package asg;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
//import java.lang.reflect.InvocationTargetException;
//import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
//import java.util.logging.Level;
//import java.util.logging.Logger;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.LineBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

public class PackageManager extends JPanel implements ActionListener, ListSelectionListener {

    PackageManager() {
        createGui();
        packageList.setCellRenderer(renderer);
        versionList.setCellRenderer(renderer);
        fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        fileChooser.setDialogTitle("Select managed directory");
    }

    private JPanel getInfoPanel() {
        JPanel ip = new JPanel();
        ip.setLayout(new BorderLayout());
        ip.add(nameVersionLabel, BorderLayout.NORTH);
        nameVersionLabel.setBackground(Color.white);
        nameVersionLabel.setOpaque(true);
        nameVersionLabel.setBorder(new LineBorder(Color.blue));
        JTabbedPane tp = new JTabbedPane();
        tp.add("Description", new JScrollPane(descriptionArea));
        dependsLabel.setBackground(Color.white);
        dependsLabel.setOpaque(true);
        tp.add("Depends", new JScrollPane(dependsLabel));
        tp.add("Files included", new JScrollPane(filesArea));
        linkLabel.setBackground(Color.white);
        linkLabel.setOpaque(true);
        tp.add("Link", new JScrollPane(linkLabel));
        ip.add(tp, BorderLayout.CENTER);
        return ip;
    }

    private void createGui() {
        setLayout(new BorderLayout());
        JPanel btnPnl = new JPanel();
        btnPnl.setBorder(new LineBorder(Color.blue));
        btnPnl.setLayout(new GridLayout(1, 3));
        upgradeBtn.addActionListener(this);
        upgradeBtn.setToolTipText("Upgrade all packages possible");
        installBtn.addActionListener(this);
        installBtn.setToolTipText("Install selected package and version");
        removeBtn.addActionListener(this);
        removeBtn.setToolTipText("Remove the selected package");
        directoryBtn.addActionListener(this);
        directoryBtn.setToolTipText("Select a different directory to manage");
        universeBtn.addActionListener(this);
        universeBtn.setToolTipText("Select a different universe for the managed directory");
        btnPnl.add(upgradeBtn);
        btnPnl.add(installBtn);
        btnPnl.add(removeBtn);
        btnPnl.add(directoryBtn);
        btnPnl.add(universeBtn);
        JPanel packagePanel = new JPanel();
        packagePanel.setLayout(new BorderLayout());
        packagePanel.add(new JLabel("Packages", SwingConstants.CENTER), BorderLayout.NORTH);
        packagePanel.add(new JScrollPane(packageList), BorderLayout.CENTER);
        packageList.addListSelectionListener(this);
        packageList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        JPanel versionPanel = new JPanel();
        versionPanel.setLayout(new BorderLayout());
        versionPanel.add(new JLabel("Versions", SwingConstants.CENTER), BorderLayout.NORTH);
        versionPanel.add(new JScrollPane(versionList), BorderLayout.CENTER);
        versionList.addListSelectionListener(this);
        versionList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        sp1 = new JSplitPane(JSplitPane.VERTICAL_SPLIT, packagePanel, versionPanel);
        sp2 = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, sp1, infoPanel);
        add(sp2, BorderLayout.CENTER);
        add(btnPnl, BorderLayout.SOUTH);
    }

    void initUniverse(String univ) {
        if (univ.contains("scala-dev")) {
            universeIdx = 0;
        } else if (univ.contains("lamp-rc")) {
            universeIdx = 1;
        } else {
            JOptionPane.showMessageDialog(this, String.format("Unrecognized universe: %s", univ),
                    "Universe error", JOptionPane.ERROR_MESSAGE);
        }
    }

    void arrangeGui() {
        if (SwingUtilities.isEventDispatchThread()) {
            sp2.setDividerLocation(0.35);
            sp1.setDividerLocation(0.7);
        } else {
            try {
                SwingUtilities.invokeAndWait(new Runnable() {
                    public void run() {
                        arrangeGui();
                    }
                });
            } catch (Throwable ex) {
                JOptionPane.showMessageDialog(Main.object,
                        String.format("%s(%s)", ex.getClass().getName(), ex.getMessage()),
                        "Error (arrangeGui)", JOptionPane.ERROR_MESSAGE);
            }
        }
    }

    void adjustButtonsAndControls() {
        adjustButtons();
        adjustControls(true);
    }

    private void adjustButtons() {
        if (SwingUtilities.isEventDispatchThread()) {
            installBtn.setEnabled(!installedPackages.containsKey(selectedPackage));
            removeBtn.setEnabled(!installBtn.isEnabled());
        } else {
            try {
                SwingUtilities.invokeAndWait(new Runnable() {
                    public void run() {
                        adjustButtons();
                    }
                });
            } catch (Exception e) {
                JOptionPane.showMessageDialog(Main.object, String.format("%s(%s)", e.getClass().getName(),
                        e.getMessage()), "Error (adjustButtons)", JOptionPane.ERROR_MESSAGE);
            }
        }
    }

    void adjustControls(final boolean enable) {
        if (SwingUtilities.isEventDispatchThread()) {
            packageList.setEnabled(enable);
            versionList.setEnabled(enable);
            directoryBtn.setEnabled(enable);
            universeBtn.setEnabled(enable);
            upgradeBtn.setEnabled(enable);
            if (enable) {
                adjustButtons();
            } else {
                installBtn.setEnabled(false);
                removeBtn.setEnabled(false);
            }
        } else {
            try {
                SwingUtilities.invokeAndWait(new Runnable() {
                    public void run() {
                        adjustControls(enable);
                    }
                });
            } catch (Exception e) {
                JOptionPane.showMessageDialog(Main.object, String.format("%s(%s)", e.getClass().getName(),
                        e.getMessage()), "Error (adjustControls)", JOptionPane.ERROR_MESSAGE);
            }
        }
    }

    private void populateVersionList() {
        versionList.removeListSelectionListener(this);
        versionModel.clear();
        for (String[] v : availablePackages) {
            if (v[0].equals(selectedPackage)) {
                selectedPackageData = v;
                for (int i = 1; i < v.length; ++i) {
                    versionModel.addElement(v[i]);
                }
                break;
            }
        }
        versionList.addListSelectionListener(this);
        if (!versionModel.isEmpty()) {
            versionList.setSelectedIndex(0);
        }
    }

    public void valueChanged(ListSelectionEvent lse) {
        Object src = lse.getSource();
        if (src == packageList) {
            selectedPackage = (String) packageList.getSelectedValue();
            installedVersion = installedPackages.containsKey(selectedPackage)
                    ? installedPackages.get(selectedPackage) : null;
            populateVersionList();
            adjustButtons();
        } else if (src == versionList) {
            selectedVersion = (String) versionList.getSelectedValue();
            new Thread() {
                public void run() {
                    showData();
                }
            }.start();
        }
    }

    private synchronized void showData() {
        Main.object.splashProgressUpdate("Populating selected package/version info");
        String showingData = String.format("%s/%s", selectedPackage, selectedVersion);
        final String[] data = Main.object.sbazConnector.showInfo(showingData);
        SwingUtilities.invokeLater(new Runnable(){
            public void run() {
                nameVersionLabel.setText(String.format("%s / %s", data[0], data[1]));
                dependsLabel.setText(data[2]);
                descriptionArea.setText(data[3]);
                filesArea.setText(data[4]);
                linkLabel.setText(data[5]);
            }
        });
        Main.object.splashGraphics = null;
    }

    public void actionPerformed(ActionEvent ae) {
        adjustControls(false);
        Object src = ae.getSource();
        Main.object.sbazConnector.setText("");
        if (src == upgradeBtn) {
            new Thread() {
                public void run() {
                    upgradePackages();
                    adjustButtonsAndControls();
                }
            }.start();
        } else if (src == installBtn) {
//            Main.object.sbazConnector.setText("");
            new Thread() {
                public void run() {
                    installPackage();
                    adjustButtonsAndControls();
                }
            }.start();
        } else if (src == removeBtn) {
//            Main.object.sbazConnector.setText("");
            new Thread() {

                public void run() {
                    removePackage();
                    adjustButtonsAndControls();
                }
            }.start();
        } else if (src == directoryBtn) {
//            Main.object.sbazConnector.setText("");
            new Thread() {

                public void run() {
                    setDirectory();
                    adjustButtonsAndControls();
                }
            }.start();
        } else if (src == universeBtn) {
//            Main.object.sbazConnector.setText("");
            new Thread() {

                public void run() {
                    setUniverse();
                    adjustButtonsAndControls();
                }
            }.start();
        }
    }

    private void setUniverse() {
        String[] options = new String[]{"scala-dev (main development universe)", "lamp-rc (release candidates from LAMP)"};
        if (JOptionPane.showConfirmDialog(this,
                String.format("Current: %s\nSwitch to: %s?", options[universeIdx], options[1 - universeIdx]),
                "Switch universe", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION) {
            String out = Main.object.sbazConnector.sbaz("setuniverse", "-i", universes[1 - universeIdx]);
            if (!Main.object.sbazConnector.hasError(out)) {
                universeIdx = 1 - universeIdx;
                resetView();
            }
        }
    }

    private void resetView1() {
        if (SwingUtilities.isEventDispatchThread()) {
            versionList.removeListSelectionListener(Main.object.packageManager);
            versionModel.clear();
            versionList.addListSelectionListener(Main.object.packageManager);
            packageList.removeListSelectionListener(Main.object.packageManager);
            packageModel.clear();
        } else {
            try {
                SwingUtilities.invokeAndWait(new Runnable() {
                    public void run() {
                        resetView1();
                    }
                });
            } catch (Exception ex) {
                JOptionPane.showMessageDialog(Main.object,
                        String.format("%s(%s)", ex.getClass().getName(), ex.getMessage()),
                        "Error (resetView1)", JOptionPane.ERROR_MESSAGE);
            }
        }
    }

    private void resetView2() {
        if (SwingUtilities.isEventDispatchThread()) {
            Main.object.setTitle(String.format("%s:%s - Another sbaz GUI", Main.object.managedDirectory == null ? "(default-scala-home)" : Main.object.managedDirectory, universeIdx == 0 ? "scala-dev" : "lamp-rc"));
            if (Main.object.splashGraphics != null) {
                Main.object.splashProgressUpdate("Populating package list");
            }
            for (String[] ap : availablePackages) {
                packageModel.addElement(ap[0]);
            }
            packageList.addListSelectionListener(Main.object.packageManager);
            if (packageModel.isEmpty()) {
                selectedPackage = null;
                selectedPackageData = null;
                installedVersion = null;
                selectedVersion = null;
            } else {
                 packageList.setSelectedIndex(0);
            }
        } else {
            try {
                SwingUtilities.invokeAndWait(new Runnable() {
                    public void run() {
                        resetView2();
                    }
                });
            } catch (Exception ex) {
                JOptionPane.showMessageDialog(Main.object,
                        String.format("%s(%s)", ex.getClass().getName(), ex.getMessage()),
                        "Error (resetView2)", JOptionPane.ERROR_MESSAGE);
            }
        }
    }

    void resetView() {
        resetView1();
        if (Main.object.splashGraphics != null) {
            Main.object.splashProgressUpdate("Checking available packages");
        }
        availablePackages = Main.object.sbazConnector.sbazAvailable();
        if (Main.object.splashGraphics != null) {
            Main.object.splashProgressUpdate("Checking installed packages");
        }
        installedPackages = Main.object.sbazConnector.sbazInstalled();
        if (Main.object.splashGraphics != null) {
            Main.object.splashProgressUpdate("Checking universe setting");
        }
        initUniverse(Main.object.sbazConnector.sbaz("showuniverse"));
        resetView2();
    }

    private void setDirectory() {
        String[] options = new String[]{"(default-scala-home)", "Custom directory"};
        String oldDirectory = Main.object.managedDirectory;
        Object resp = JOptionPane.showInputDialog(this, "Select default or custom managed-directory", "Set managed-directory",
                JOptionPane.QUESTION_MESSAGE, null, options, options[0]);
        if (resp == options[0]) {
            if (Main.object.managedDirectory != null) {
                Main.object.managedDirectory = null;
                //resetView();
            }
        } else if (resp == options[1]) {
            int returnVal = fileChooser.showOpenDialog(this);
            if (returnVal == JFileChooser.APPROVE_OPTION) {
                File md = fileChooser.getSelectedFile();
                if (!md.getAbsolutePath().equals(oldDirectory)) {
                    Main.object.managedDirectory = md.getAbsolutePath();
                    File[] fList = md.listFiles();
                    if (fList.length == 0) {
                        String out = Main.object.sbazConnector.sbaz("setup");
                        if (Main.object.sbazConnector.hasError(out)) {
                            Main.object.managedDirectory = oldDirectory;
                        } else {
                            out = Main.object.sbazConnector.sbaz("setuniverse", "-i",
                                    "<simpleuniverse><name>scala-dev</name><location>http://scala-webapps.epfl.ch/sbaz/scala-dev</location></simpleuniverse>");
                            if (Main.object.sbazConnector.hasError(out)) {
                                Main.object.managedDirectory = oldDirectory;
                            }
                        }
                    } else {
                        boolean hasMeta = false;
                        for (File f : fList) {
                            if (f.getName().equals("meta")) {
                                hasMeta = true;
                            }
                        }
                        if (!hasMeta) {
                            Main.object.managedDirectory = oldDirectory;
                            JOptionPane.showMessageDialog(this, md.getAbsolutePath() + " is not an sbaz managed-directory",
                                    "Error - bad directory", JOptionPane.ERROR_MESSAGE);
                        }
                    }
                }
            }
        }
        if ((Main.object.managedDirectory == null && oldDirectory != null) || (Main.object.managedDirectory != null && !Main.object.managedDirectory.equals(oldDirectory))) {
            resetView();
        }
    }

    private void upgradePackages() {
        String output = Main.object.sbazConnector.sbaz("upgrade");
        if (Main.object.sbazConnector.hasError(output)) {
            return;
        }
        installedPackages = Main.object.sbazConnector.sbazInstalled();
        installedVersion = installedPackages.containsKey(selectedPackage)
                ? installedPackages.get(selectedPackage) : null;
        repaint();
        //versionList.repaint();
        //packageList.repaint();
    }

    private void installPackage() {
        String output = Main.object.sbazConnector.sbaz("install",
                String.format("%s/%s", selectedPackage, selectedVersion));
        if (Main.object.sbazConnector.hasError(output)) {
            return;
        }
        installedPackages = Main.object.sbazConnector.sbazInstalled();
        installedVersion = installedPackages.containsKey(selectedPackage)
                ? installedPackages.get(selectedPackage) : null;
        versionList.repaint();
        packageList.repaint();
    }

    private void removePackage() {
        String title = String.format("Removing %s", selectedPackage);
        if (selectedPackage.equals("sbaz")) {
            JOptionPane.showMessageDialog(this, "<html><font color=\"red\">Removing sbaz will disable this program.</font><br/><br/>"
                    + "Please use command-line tools to remove sbaz.</html>",
                    title, JOptionPane.WARNING_MESSAGE);
            return;
        } else if (selectedPackage.equals("asg")) {
            JOptionPane.showMessageDialog(this, "<html><font color=\"red\">You are attempting to uninstall this program.</font><br/><br/>"
                    + "Please use 'sbaz remove asg' from the command-line.</html>",
                    title, JOptionPane.WARNING_MESSAGE);
            return;
        }
        String msg = Main.object.corePackages.contains(selectedPackage)
                ? String.format("<html><font color=\"red\">Removing '%s' will damage the Scala installation</font><br/><br/>", selectedPackage)
                + String.format("Do you really want to remove package '%s' ? </html>", selectedPackage)
                : String.format("OK to remove '%s' ?", selectedPackage);
        if (JOptionPane.showConfirmDialog(this, msg, title, JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {
            return;
        }
        String output = Main.object.sbazConnector.sbaz("remove", selectedPackage);
        if (Main.object.sbazConnector.hasError(output)) {
            return;
        }
        installedPackages = Main.object.sbazConnector.sbazInstalled();
        installedVersion = installedPackages.containsKey(selectedPackage)
                ? installedPackages.get(selectedPackage) : null;
        versionList.repaint();
        packageList.repaint();
    }
    JButton upgradeBtn = new JButton("Upgrade", AllIcons.upgradeIcon), installBtn = new JButton("Install", AllIcons.loadIcon),
            removeBtn = new JButton("Remove", AllIcons.eraseIcon), directoryBtn = new JButton("Directory", AllIcons.databaseIcon),
            universeBtn = new JButton("Universe", AllIcons.globeIcon);
    private DefaultListModel packageModel = new DefaultListModel(),
            versionModel = new DefaultListModel();
    JList packageList = new JList(packageModel), versionList = new JList(versionModel);
    List<String[]> availablePackages = null;
    Map<String, String> installedPackages = null;
    String selectedPackage = null;
    String[] selectedPackageData = null;
    String selectedVersion = null;
    String installedVersion = null;
    private JSplitPane sp1, sp2;
    private SbazRenderer renderer = new SbazRenderer();
//    private String showingData = null;
    private JLabel nameVersionLabel = new JLabel("- / -", SwingConstants.CENTER);
    private JTextArea descriptionArea = new JTextArea();
    private JLabel dependsLabel = new JLabel(" ", SwingConstants.CENTER);
    private JTextArea filesArea = new JTextArea();
    private JLabel linkLabel = new JLabel(" ", SwingConstants.CENTER);
    private JPanel infoPanel = getInfoPanel();
    private JFileChooser fileChooser = new JFileChooser();
    private String[] universes = {
        "<simpleuniverse><name>scala-dev</name><location>http://scala-webapps.epfl.ch/sbaz/scala-dev</location></simpleuniverse>",
        "<simpleuniverse><name>lamp-rc</name><location>http://scala-webapps.epfl.ch/sbaz/lamp-rc</location></simpleuniverse>"
    };
    private int universeIdx = 0;
}
